<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    分支切换和 cleanup
  </body>

  <script>
    // 用一个全局变量存储被注册的副作用函数
    let activeEffect;
    // effect 函数用于注册副作用函数
    function effect(fn) {
      const effectFn = () => {
        cleanup(effectFn);
        // 当 effectFn 执行时，将其设置为当前激活的副作用函数
        activeEffect = effectFn;
        fn();
      };
      effectFn.deps = [];
      // 执行副作用函数
      effectFn();
    }
    // 存储副作用函数的桶
    // WeakMap: target -> Map
    // Map: key -> Set
    const bucket = new WeakMap();
    // 原始数据
    const data = { ok: true, text: "hello world" };
    // 对原始数据进行代理
    const obj = new Proxy(data, {
      // 拦截读取操作
      get(target, key) {
        // 将副作用函数 activeEffect 添加到桶中
        track(target, key);
        // 返回属性值
        return target[key];
      },
      // 拦截设置操作
      set(target, key, newVal) {
        // 设置属性值
        target[key] = newVal;
        // 把副作用函数从桶中取出来并执行
        trigger(target, key);
      },
    });

    // 在 get 拦截函数内调用 track 函数，追踪变化
    function track(target, key) {
      // 没有 activeEffect 直接返回对应的值
      if (!activeEffect) return;

      // 根据 target 从桶中获取 depsMap
      let depsMap = bucket.get(target);
      // 如果不存在，那么新建一个 Map 和 target 进行关联
      if (!depsMap) {
        bucket.set(target, (depsMap = new Map()));
      }

      // 再根据 key 从 depsMap 中获取 deps
      let deps = depsMap.get(key);
      // 若 deps 不存在，则创建一个 Set 和 key 进行关联
      if (!deps) {
        depsMap.set(key, (deps = new Set()));
      }
      // 将当前副作用函数添加桶里
      deps.add(activeEffect);

      activeEffect.deps.push(deps);
    }

    // 在 set 拦截函数内调用 trigger 函数，触发变化
    function trigger(target, key) {
      // 根据 target 从桶中获取 depsMap
      const depsMap = bucket.get(target);
      if (!depsMap) return;
      // 根据 key 取得所有副作用函数 effets
      const effects = depsMap.get(key);

      const effectsToRun = new Set(effects);
      // 执行副作用函数
      effectsToRun.forEach((fn) => fn());
    }

    function cleanup(effectFn) {
      for (let index = 0; index < effectFn.deps.length; index++) {
        const deps = effectFn.deps[index];
        deps.delete(effectFn);
      }

      effectFn.deps.length = 0;
    }

    effect(() => {
      document.body.innerHTML = obj.ok ? obj.text : "not";
    });

    setTimeout(() => {
      obj.ok = false;
      obj.text = "hello world 3";
    }, 1000);
  </script>
</html>
